package cn.edu.jssvc.litai.mygallery;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.util.Log;
import android.util.LruCache;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ArrayAdapter;
import android.widget.GridView;
import android.widget.ImageView;

import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;

public class GalleryAdapter extends ArrayAdapter<String>
        implements AbsListView.OnScrollListener {
    // 保存正在下载或等待下载的异步任务对象的集合。
    private Set<BitmapWorkerTask> taskCollection;
    // 声明图片缓存类对象，用于缓存所有下载好的图片，
    // 在程序内存达到设定值时会将最近最少使用的图片移除掉。
    private LruCache<String, Bitmap> mMemoryCache;
    // 声明GridView的实例变量
    private GridView gridView;
    // GridView中第一个可见单元索引号
    private int mFirstVisibleItem;
    // GridView中可见单元数量
    private int mVisibleItemCount;
// GridView中单元总数量
private int mTotalItemCount;
    // 记录是否刚打开程序，用于解决进入程序不滚动屏幕，不会下载图片的问题。
    private boolean isFirstEnter = true;
    /**
     * 方法名称：GalleryAdapter
     * <p>方法功能：类构造方法
     * @param context Context类型，指定应用运行环境对象
     * @param textViewResourceId int类型，引用单元布局中TextView组件ID
     * @param objects String[]类型，接收数据集
     * @param gridView GridView，显示数据集的Adapter视图组件
     */
    public GalleryAdapter(Context context, int textViewResourceId,
                          String[] objects, GridView gridView) {
        super(context, textViewResourceId, objects);
        // 传入显示画廊的GridView对象
        this.gridView = gridView;
        // 新建管理后台下载任务对象集合，因为对于每个图片URL
        // 都会创建一个后台下载任务
        taskCollection = new HashSet<BitmapWorkerTask>();
        // 获取应用程序最大可用内存
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        int cacheSize = maxMemory / 8;
        Log.d("位图缓冲区容量",String.valueOf(cacheSize));
        // 设置图片缓存大小为程序最大可用内存的1/8
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                int size = bitmap.getByteCount();
                Log.d("当前位图字节数", String.valueOf(size));
                return size;
            }
        };
        // 为GridView设置滚动事件监听器
        this.gridView.setOnScrollListener(this);
    }
    /**
     * 方法名称：getView
     * <p>方法功能：为数据集中指定位置数据加载该单元布局，
     * 并将数据显示到单元布局中视图上。
     * @param position int类型，数据集中数据位置
     * @param convertView View，暂存负责显示当前数据的视图作为缓冲
     * @param parent ViewGroup，当前数据视图父布局，
     * 当加载视图布局直接作为单元根布局时，可以不使用
     * @return 返回加载成功的单元布局
     */
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // 取得数据集中当前位置数据
        final String url = getItem(position);
        View view;
        if (convertView == null) {// 如果暂存单元布局为null,加载当前单元布局
            view = LayoutInflater.from(getContext())
                    .inflate(R.layout.grid_cell_layout, null);
        } else {// 如果暂存单元布局存在，直接将其作为当前单元布局
            view = convertView;
        }
        final ImageView imageView = (ImageView) view
                .findViewById(R.id.picture);
        // 为了区分GridView中的ImageView，给每个ImageView设置一个Tag（标签）
        // tag就用图片的URL字符串，这样加载图片时可以让GridView中的ImageView
        // 与加载的图片对应
        imageView.setTag(url);
        setImageView(url, imageView);
        return view;
    }
    /**
     * <p>方法名称：setImageView</p>
     * <p>方法功能：给GridView单元布局中ImageView设置图片。
     * 先尝试从LruCache中取出图片的缓存，设置到ImageView上。
     * 如果LruCache中没有该图片的缓存，就给ImageView设置一张默认背景图。
     * @param imageUrl String类型，图片的URL地址，用于作为LruCache的键。
     * @param imageView ImageView类型，显示图片的控件。
     */
    private void setImageView(String imageUrl, ImageView imageView) {
        // 以图片的URL字符串为键名从LruCache中提取位图
        Bitmap bitmap = getBitmapFromMemoryCache(imageUrl);
        if (bitmap != null) {
            // LruCache中有该图片缓存，将图片设置到ImageView上
            imageView.setImageBitmap(bitmap);
        } else {
            // 如果LruCache中没有该图片的缓存，就给ImageView设置一张默认占位图
            imageView.setImageResource(R.drawable.ic_launcher_background);
        }
    }
    /**
     * <p>方法名称：addBitmapToMemoryCache</p>
     * <p>方法功能：将一张位图存储到LruCache中。</p>
     *
     * @param key String类型，LruCache的键，这里传入图片的URL地址。
     * @param bitmap Bitmap类型，LruCache的值，这里传入从网络上下载的Bitmap对象。
     */
    public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
        if (getBitmapFromMemoryCache(key) == null) {
            mMemoryCache.put(key, bitmap);
        }
    }
/**
 * <p><b>方法名称：getBitmapFromMemoryCache</b></p>
 * <p>方法功能：根据参数传从的键名，从LruCache中获取一张图片，
 * 如果不存在就返回null。</p>
 *
 * @param key String类型，LruCache的键，这里传入图片的URL地址。
 * @return 返回值，与参数传入键对应的Bitmap对象，或者null。
 */
public Bitmap getBitmapFromMemoryCache(String key) {
    return mMemoryCache.get(key);
}
    /**
     * <p>方法名称：onScrollStateChanged</p>
     * <p>方法功能：GridView滚动状态事件回调方法，检查当前滚动状态，
     * 会在显示一幅画面前调用，即比getView()方法先调用</p>
     * @param view AbsListView类型，发生滚动事件GridView对象
     * @param scrollState int，滚动状态，即：SCROLL_STATE_TOUCH_SCROLL
     * 或 SCROLL_STATE_IDLE.
     */
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        // 仅当GridView静止时才去下载图片，GridView滑动时取消所有正在下载的任务
        if (scrollState == SCROLL_STATE_IDLE) {
            loadBitmaps(mFirstVisibleItem, mVisibleItemCount);
        } else {
            cancelAllTasks();
        }
    }
    /**
     * <p>方法名称：onScroll</p>
     * <p>方法功能：GridView中单元滚动时的回调方法，即滚动事件监听方法</p>
     * @param view AbsListView类型，发生滚动事件GridView对象
     * @param firstVisibleItem int类型，第一个可见单元的索引号
     * @param visibleItemCount int类型，可见单元数
     * @param totalItemCount int,GridView单元总数
     * @return 无返回值
     */
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem,
                         int visibleItemCount, int totalItemCount) {
        mFirstVisibleItem = firstVisibleItem;
        Log.d("GridView第1个可见单元索引号",
                String.valueOf(mFirstVisibleItem));
        mVisibleItemCount = visibleItemCount;
        Log.d("GridView可见单元数", String.valueOf(mVisibleItemCount));
        mTotalItemCount = totalItemCount;
        Log.d("GridView单元总数", String.valueOf( mTotalItemCount));
        // 下载的任务应该由onScrollStateChanged里调用，
// 但首次进入程序时onScrollStateChanged并不会调用，
        // 因此在这里为首次进入程序开启下载任务。
        if (isFirstEnter && visibleItemCount > 0) {
            loadBitmaps(firstVisibleItem, visibleItemCount);
            isFirstEnter = false;
        }
    }
    /**
     * <p>方法名称：loadBitmaps</p>
     * <p>方法功能：加载Bitmap对象。
     * 通过循环检查当前可见的ImageView的Bitmap对象是否
     * 在LruCache中，如果已经存在，则直接加载位图；否则，开启异步任务下载图片。</p>
     *<p>方法参数：</p>
     * @param firstVisibleItem int类型，第一个可见的ImageView的索引号
     * @param visibleItemCount int类型，当前可见的图片单元数目
     * @return 无返回值
     */
    private void loadBitmaps(int firstVisibleItem, int visibleItemCount) {
        try {
            for (int i = firstVisibleItem;
                 i < firstVisibleItem + visibleItemCount; i++) {
                // 从图片URL数组中提取一张当前可见图片的URL字符串
                String imageUrl = PituresArray.imageUrls[i];
                // 以图片的URL字符串为键名从LruCache中提取位图
                Bitmap bitmap = getBitmapFromMemoryCache(imageUrl);
                if (bitmap == null) {// 如果缓冲区中指定的位图不存在
                    // 新建一个下载图片的后台异步任务
                    BitmapWorkerTask task = new BitmapWorkerTask();
                    // 将新建的异步任务保存到集合中，以便应用结束后销毁异步任务
                    taskCollection.add(task);
                    // 启动当前新建的异步任务
                    task.execute(imageUrl);
                } else {// 如果缓冲区中指定的位图已存在
                    // 取得当前图片对应的ImageView,注意这里使用Tag找视图的方法，
// 而不用ID找视图的方法
                    // ImageView的Tag就是前面getView()方法中设置的图片URL字符串
                    ImageView imageView = (ImageView) gridView
                            .findViewWithTag(imageUrl);
                    // 如果显示用ImageView及对应图片位图都已经存在，则显示该位图
                    if (imageView != null && bitmap != null) {
                        imageView.setImageBitmap(bitmap);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * <p>方法名称：cancelAllTasks</p>
     * <p>方法功能：遍历保存有后台下载异步任务的集合，
     * 取消所有异步任务。</p>
     */
    public void cancelAllTasks() {
        if (taskCollection != null) {
            for (BitmapWorkerTask task : taskCollection) {
                task.cancel(false);
            }
        }
    }
    /**
     * <p>类名称：BitmapWorkerTask</p>
     * <p>类功能：后台异步任务负责通过网络连接下载图片，
     * 并将图片位图传递给前台任务显示。</p>
     */
    class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
        // 图片的URL地址
        private String imageUrl;
        /**
         * <p>方法名称：doInBackground</p>
         * <p>方法功能：后台异步任务。
         * 接收传入的图片URL,下载图片，
         * 将图片解码为Bitmap格式位图，
         * 将位图返回给前台UI线程显示</p>
         * @param params String类型变长参数，接收图片URL
         * @return Bitmap类型，图片位图
         */
        @Override
        protected Bitmap doInBackground(String... params) {
            imageUrl = params[0];
            // 下载图片
            Bitmap bitmap = downloadBitmap(params[0]);
            // 如果图片下载成功，将其保存到LrcCache中，
            // 缓存中key为图片的URL字符串
            if (bitmap != null) {
                addBitmapToMemoryCache(params[0], bitmap);
            }
            return bitmap;
        }
        /**
         * <p>方法名称：onPostExecute()</p>
         * <p>方法功能：这是AsyncTask类中运行在UI主线程中的方法，
         * 负责接收后台任务返回值，这里就是接收doInBackground()
         * 方法返回的Bitmapc对象，将位图显示在GridView中相应的
         * ImageView中。</p>
         * @param bitmap Bitmap类型参数，接收后台任务返回值。
         */
        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            // 根据Tag找到相应的ImageView控件，将下载好的图片显示出来。
            ImageView imageView = (ImageView) gridView
                    .findViewWithTag(imageUrl);
            if (imageView != null && bitmap != null) {
                imageView.setImageBitmap(bitmap);
            }
            taskCollection.remove(this);
        }
        /**
         * <p>方法名称：downloadBitmap（）</p>
         * <p>方法功能：按照参数提供的图片URL，使用HTTP协议
         * 连接服务器，获取图片对象</p>。
         *
         * @param imageUrl String类型的参数，接收图片的URL地址
         * @return Bitmap类型返回值，下载得到的图片对象
         */
        private Bitmap downloadBitmap(String imageUrl) {
            Bitmap bitmap = null;
            HttpURLConnection con = null;
            try {
                URL url = new URL(imageUrl);
                con = (HttpURLConnection) url.openConnection();
                con.setConnectTimeout(5 * 1000);
                con.setReadTimeout(10 * 1000);
                bitmap = BitmapFactory.decodeStream(con.getInputStream());
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (con != null) {
                    con.disconnect();
                }
            }
            return bitmap;
        }
    }
}
